/*
 ------------------------------------------------------------------------------
 Copyright (C) 2006-2007 Team Blur.

 This file is part of the Quantum Engine source code.

 The Quantum Engine source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quantum Engine source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quantum Engine source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


#include "s_local.h"


/*
 ==================
 S_PickChannel
 ==================
*/
channel_t *S_PickChannel (int listenerId, int channelId, soundShader_t *soundShader, bool local, emitter_t *emitter){

	channel_t	*channel;
	int			index = -1;
	int			i;

	if (!soundShader)
		Com_Error(false, "S_PickChannel: NULL soundShader");

	// Some sounds can't be repeated
	if (soundShader->flags & SSF_NODUPS){
		for (i = 0, channel = snd.channels; i < snd.numChannels; i++, channel++){
			if (!channel->sound)
				continue;

			// Don't play the same sound if already playing from the same
			// emitter
			if (channel->soundShader == soundShader && channel->local == local && channel->emitter == emitter){
				Com_DPrintf(S_COLOR_RED "S_PickChannel: dropped sound '%s'\n", soundShader->name);
				return NULL;
			}
		}
	}

	// Try to find a free channel
	for (i = 0, channel = snd.channels; i < snd.numChannels; i++, channel++){
		if (!channel->sound){
			index = i;
			break;
		}
	}

	// If we didn't find a free channel
	if (index == -1){
		// Try to override an active channel
		for (i = 0, channel = snd.channels; i < snd.numChannels; i++, channel++){
			// Some sounds can't be overridden
			if (channel->soundShader->flags & SSF_NOSTOP)
				continue;

			// Never override sounds with higher priority
			if (channel->soundShader->priority > soundShader->priority)
				continue;

			// Never override local player sounds with other sounds
			if (channel->listenerId == snd.listener.listenerId && listenerId != snd.listener.listenerId)
				continue;

			// Always override sounds from the same channel and emitter
			if (channelId != SOUND_CHANNEL_ANY){
				if (channel->channelId == channelId && channel->local == local && channel->emitter == emitter){
					// Some sounds can't be restarted
					if (channel->soundShader->flags & SSF_PLAYONCE){
						if (channel->soundShader == soundShader)
							continue;
					}

					index = i;
					break;
				}
			}

			// If we don't have a channel yet, override this one
			if (index == -1){
				index = i;
				continue;
			}

			// Always override sounds with lower priority
			if (channel->soundShader->priority < snd.channels[index].soundShader->priority){
				index = i;
				continue;
			}

			// Always override the oldest sounds if they have equal priority
			if (channel->soundShader->priority == snd.channels[index].soundShader->priority){
				if (channel->allocTime < snd.channels[index].allocTime){
					index = i;
					continue;
				}
			}
		}
	}

	// Do we have a channel?
	if (index == -1){
		Com_DPrintf(S_COLOR_RED "S_PickChannel: dropped sound '%s'\n", soundShader->name);
		return NULL;
	}

	channel = &snd.channels[index];

	// If overriding an active channel, make sure it is stopped
	if (channel->sound){
		Com_DPrintf(S_COLOR_YELLOW "S_PickChannel: overridden sound '%s'\n", channel->soundShader->name);

		qalSourceStop(channel->sourceId);
	}

	// Set up the channel
	channel->sound = NULL;

	channel->listenerId = listenerId;
	channel->channelId = channelId;
	channel->allocTime = Sys_Milliseconds();

	channel->soundShader = soundShader;
	channel->local = local;
	channel->emitter = emitter;

	return channel;
}

/*
 ==================
 S_PlayChannel
 ==================
*/
void S_PlayChannel (channel_t *channel){

	soundShader_t	*soundShader = channel->soundShader;
	int				offset;

	if (channel->sound)
		return;		// Already active

	if (s_logFile->integerValue)
		QAL_LogPrintf("----- S_PlayChannel ( %s ) -----\n", soundShader->name);

	channel->sound = soundShader->sounds[M_RandInt() % soundShader->numSounds];

	// Rewind the source
	qalSourceRewind(channel->sourceId);

	// Set up the sound
	qalSourcei(channel->sourceId, AL_BUFFER, channel->sound->bufferId);

	// Set up looping and offset parameters
	if (soundShader->flags & SSF_LOOPING){
		if (soundShader->flags & SSF_NOOFFSET)
			offset = 0;
		else {
			if (channel->local)
				offset = M_Ftol(channel->sound->rate * MS2SEC(snd.frameTime)) % channel->sound->samples;
			else
				offset = M_Ftol(channel->sound->rate * MS2SEC(snd.listener.time)) % channel->sound->samples;
		}

		qalSourcei(channel->sourceId, AL_LOOPING, AL_TRUE);
		qalSourcei(channel->sourceId, AL_SAMPLE_OFFSET, offset);
	}
	else {
		qalSourcei(channel->sourceId, AL_LOOPING, AL_FALSE);
		qalSourcei(channel->sourceId, AL_SAMPLE_OFFSET, 0);
	}

	// Special case for non-spatialized local sounds
	if (channel->local){
		qalSourcei(channel->sourceId, AL_SOURCE_RELATIVE, AL_TRUE);

		qalSourcefv(channel->sourceId, AL_POSITION, vec3_origin.ToFloatPtr());
		qalSourcefv(channel->sourceId, AL_VELOCITY, vec3_origin.ToFloatPtr());

		qalSourcef(channel->sourceId, AL_PITCH, soundShader->pitch);
		qalSourcef(channel->sourceId, AL_REFERENCE_DISTANCE, 0.0f);
		qalSourcef(channel->sourceId, AL_MAX_DISTANCE, 0.0f);
		qalSourcef(channel->sourceId, AL_ROLLOFF_FACTOR, 0.0f);

		// Disable occlusion
		if (snd.efx.occlusion)
			qalSourcei(channel->sourceId, AL_DIRECT_FILTER, 0);

		// Disable reverb
		if (snd.efx.reverb)
			qalSource3i(channel->sourceId, AL_AUXILIARY_SEND_FILTER, 0, 0, 0);

		return;
	}

	// Set up spatialization parameters
	if (soundShader->flags & (SSF_GLOBAL | SSF_OMNIDIRECTIONAL))
		qalSourcei(channel->sourceId, AL_SOURCE_RELATIVE, AL_TRUE);
	else
		qalSourcei(channel->sourceId, AL_SOURCE_RELATIVE, AL_FALSE);

	qalSourcef(channel->sourceId, AL_PITCH, soundShader->pitch);
	qalSourcef(channel->sourceId, AL_REFERENCE_DISTANCE, 0.0f);
	qalSourcef(channel->sourceId, AL_MAX_DISTANCE, 0.0f);
	qalSourcef(channel->sourceId, AL_ROLLOFF_FACTOR, 0.0f);
}

/*
 ==================
 S_StopChannel
 ==================
*/
void S_StopChannel (channel_t *channel){

	soundShader_t	*soundShader = channel->soundShader;

	if (!channel->sound)
		return;		// Not active

	if (s_logFile->integerValue)
		QAL_LogPrintf("----- S_StopChannel ( %s ) -----\n", soundShader->name);

	channel->sound = NULL;

	// Stop the source
	qalSourceStop(channel->sourceId);
}

/*
 ==================
 S_UpdateChannel
 ==================
*/
void S_UpdateChannel (channel_t *channel){

	soundShader_t	*soundShader = channel->soundShader;
	float			distance, occludedDistance;
	float			attenuation, volume = 1.0f;
	uint			directFilter = 0, reverbFilter = 0, effect = 0;
	int				state;

	if (!channel->sound)
		return;		// Not active

	if (s_logFile->integerValue)
		QAL_LogPrintf("----- S_UpdateChannel ( %s ) -----\n", soundShader->name);

	qalGetSourcei(channel->sourceId, AL_SOURCE_STATE, &state);

	// If done playing, free the channel and return immediately
	if (state == AL_STOPPED){
		channel->sound = NULL;
		return;
	}

	// Special case for non-spatialized local sounds
	if (channel->local){
		qalSourcef(channel->sourceId, AL_GAIN, soundShader->volume * s_effectsVolume->floatValue);

		// Play the source if needed
		if (state != AL_PLAYING)
			qalSourcePlay(channel->sourceId);

		return;
	}

	// Development tools
	if (s_skipEmitters->integerValue)
		volume = 0.0f;
	else {
		if (s_singleEmitter->integerValue >= 0){
			if (channel->emitter){
				if (s_singleEmitter->integerValue != channel->emitter->index)
					volume = 0.0f;
			}
			else
				volume = 0.0f;
		}
	}

	// Update the channel if needed
	if (channel->emitter){
		channel->position = channel->emitter->position;
		channel->velocity = channel->emitter->velocity;

		channel->underwater = channel->emitter->underwater;
		channel->area = channel->emitter->area;
	}

	// Calculate parameters
	if (s_skipAttenuation->integerValue || (soundShader->flags & SSF_GLOBAL)){
		// Account for reverb
		if (!s_skipReverb->integerValue && !(soundShader->flags & SSF_NOREVERB))
			effect = snd.efx.effectId;
	}
	else {
		// Find the distance to the listener
		distance = snd.listener.position.DistanceFast(channel->position);
		distance = M_ClampFloat(distance, soundShader->minDistance, soundShader->maxDistance);

		// Account for occlusion
		if (!s_skipOcclusion->integerValue && !(soundShader->flags & SSF_NOOCCLUSION)){
			if (S_SoundIsOccluded(channel->area, distance, soundShader->maxDistance, &occludedDistance)){
				distance = occludedDistance;

				directFilter = snd.efx.directFilterId;
				reverbFilter = snd.efx.reverbFilterId;
			}
		}

		// Account for reverb
		if (!s_skipReverb->integerValue && !(soundShader->flags & SSF_NOREVERB))
			effect = snd.efx.effectId;

		// Compute distance attenuation
		if (soundShader->maxDistance > soundShader->minDistance && soundShader->rolloffFactor){
			attenuation = 1.0f - (soundShader->rolloffFactor * (distance - soundShader->minDistance) / (soundShader->maxDistance - soundShader->minDistance));

			volume *= M_ClampFloat(attenuation, 0.0f, 1.0f);
		}

		// Account for underwater
		if (snd.listener.underwater || channel->underwater){
			volume *= 0.5f;

			directFilter = snd.efx.directFilterId;
			reverbFilter = snd.efx.reverbFilterId;
		}
	}

	// Update spatialization parameters
	if (soundShader->flags & (SSF_GLOBAL | SSF_OMNIDIRECTIONAL)){
		qalSourcefv(channel->sourceId, AL_POSITION, vec3_origin.ToFloatPtr());
		qalSourcefv(channel->sourceId, AL_VELOCITY, vec3_origin.ToFloatPtr());
	}
	else {
		qalSourcefv(channel->sourceId, AL_POSITION, channel->position.ToFloatPtr());
		qalSourcefv(channel->sourceId, AL_VELOCITY, (channel->velocity * soundShader->dopplerFactor).ToFloatPtr());
	}

	qalSourcef(channel->sourceId, AL_GAIN, volume * soundShader->volume * s_effectsVolume->floatValue);

	// Update occlusion
	if (snd.efx.occlusion){
		if (volume)
			qalSourcei(channel->sourceId, AL_DIRECT_FILTER, directFilter);
		else
			qalSourcei(channel->sourceId, AL_DIRECT_FILTER, 0);
	}

	// Update reverb
	if (snd.efx.reverb){
		if (volume)
			qalSource3i(channel->sourceId, AL_AUXILIARY_SEND_FILTER, effect, 0, reverbFilter);
		else
			qalSource3i(channel->sourceId, AL_AUXILIARY_SEND_FILTER, 0, 0, 0);
	}

	// Play the source if needed
	if (state != AL_PLAYING)
		qalSourcePlay(channel->sourceId);
}


// ============================================================================


/*
 ==================
 S_ListActiveChannels_f
 ==================
*/
static void S_ListActiveChannels_f (void){

	channel_t	*channel;
	int			channels = 0;
	int			i;

	Com_Printf("Current active channels:\n");

	for (i = 0, channel = snd.channels; i < snd.numChannels; i++, channel++){
		if (!channel->sound)
			continue;

		channels++;

		Com_Printf("%3i: %s\n", i+1, channel->soundShader->name);
	}

	Com_Printf("--------------------\n");
	Com_Printf("%i active channels\n", channels);
}

/*
 ==================
 S_InitChannels
 ==================
*/
void S_InitChannels (void){

	channel_t	*channel;
	int			i, maxChannels;

	// Register our commands
	Cmd_AddCommand("listActiveChannels", S_ListActiveChannels_f, "Lists active channels");

	// Create the channels
	if (s_maxChannels->integerValue > 0)
		maxChannels = M_ClampLong(s_maxChannels->integerValue, 32, MAX_SOUND_CHANNELS);
	else
		maxChannels = MAX_SOUND_CHANNELS;

	for (i = 0, channel = snd.channels; i < maxChannels; i++, channel++){
		qalGenSources(1, &channel->sourceId);

		if (qalGetError() != AL_NO_ERROR)
			break;

		snd.numChannels++;
	}
}

/*
 ==================
 S_ShutdownChannels
 ==================
*/
void S_ShutdownChannels (void){

	channel_t	*channel;
	int			i;

	// Unregister our commands
	Cmd_RemoveCommand("listActiveChannels");

	// Delete all channels
	for (i = 0, channel = snd.channels; i < snd.numChannels; i++, channel++)
		qalDeleteSources(1, &channel->sourceId);
}
